package estadistec.logica.parser;

import estadistec.logica.tipos.*;
import estadistec.logica.tabla.Atributo;

/**
 * Esta clase sirve como estructura de datos lista para ser
 * evaluada, contiene todos los datos y los códigos de funciones
 * necesarios, abstraidos mediante métodos.
 *
 * En el caso que no haya más datos o valores, devuelve una excepción
 * de no más datos disponibles.
 */


public class EstructuraEvaluable
{
	/**
	 * Si el nodo actual es una función, hijoIzq se usa como
	 * primer parámetro para dicha función
	 */
	protected EstructuraEvaluable hijoIzq;
	/**
	 * Si el nodo actual es una función, hijoDer se usa como
	 * segundo parámetro para dicha función
	 */
	protected EstructuraEvaluable hijoDer;
	/**
	 * Tipo del resultado de la evaluación de este nodo
	 */
	protected DataObjectTipos tipo;
	/**
	 * Si este nodo corresponde a una constante, corresponde a dicha
	 * constante, de otro modo corresponde al resultado de la evaluación
	 * de esta estructura.
	 */
	protected Object valor;
	/**
	 * Si este nodo corresponde a una función, este campo tendrá
	 * un valor siempre positivo, que corresponde al id para de la
	 * función para su ejecución.
	 */
	protected int idFuncion;

	EstructuraEvaluable(int idFuncion, Object valor, DataObjectTipos tipo)
	{
		this.hijoIzq = this.hijoDer = null;
		this.valor =  valor;
		this.idFuncion = idFuncion;
		this.tipo = tipo;
	}

	public EstructuraEvaluable()
	{
		this.hijoIzq = this.hijoDer = null;
		this.idFuncion = Integer.MIN_VALUE;
	}

	/** El clone es importante pues las evaluaciones
	 * no se pueden realizar directamente sobre esta
	 * estructura sino en una copia de esta estructura,
	 * para que los parámetros de las operaciones siguientes
	 * puedan ser incluidas con toda tranquilidad en la
	 * estructura copiada.
	 */
	EstructuraEvaluable clonar()
	{
		EstructuraEvaluable salida = new EstructuraEvaluable();

		salida.tipo = this.tipo;
		salida.valor = this.valor;
		salida.idFuncion = this.idFuncion;

		if(hijoIzq != null)
		{
			salida.hijoIzq = this.hijoIzq.clonar();
			if(hijoDer != null)
			{
				salida.hijoDer = this.hijoDer.clonar();
			}
		}

		return salida;
	}

	/**
	 * Evalúa la fórmula ingresada por el usuario.  Llena automáticamente al IEvaluable
	 *  con los resultados de dicha evaluación.
	 * @param atributoActual es el atributo al que pertenece esta estrutura evaluable y
	 *  al cual se le asignarán los resultados de la evaluación de la fórmula.
	 */
	public void evaluar(IEvaluable atributoActual)
	{
		evaluarInterno(atributoActual.getNoCasos());
		boolean yaEsLista = false;
		
		switch(this.tipo)
		{
			case LISTA_ENTERO:
			case LISTA_BOOL:
			case LISTA_RANGO:
			case LISTA_FLOTANTE:
			case LISTA_CUALITATIVO:
				//si es una lista, no hay problema, se deja así y ya...
				yaEsLista = true;
				break;
			default:
				break;
		}
		
		//llena los valores del atributo con el resultado
		if(yaEsLista)
		{
			IEvaluable valorResultado = (IEvaluable) this.valor;
			for(int i=0; i<atributoActual.getNoCasos(); i++)
			{
				atributoActual.setValor(i, valorResultado.getValor(i));
			}
		}else
		{
			//si es constante itera sobre todos los valores asignándoles el valor resultado
			for(int i=0; i<atributoActual.getNoCasos(); i++)
			{
				atributoActual.setValor(i, this.valor);
			}
		}
	}
	
	/**
	 * Realiza la evaluación (real) de esta estructura y de sus
	 * 'subestructuras' hijas.
	 */
	private void evaluarInterno(int cantResultados)
	{
		switch(this.tipo)
		{
			case BOOL:
			case ENTERO:
			case FLOTANTE:
			case CUALITATIVO:
			case RANGO:
			case OPERADOR:
			case FUNCION:
				if(this.hijoIzq != null)
				{
					this.hijoIzq.evaluarInterno(cantResultados);
					if(this.hijoDer != null)
					{
						this.hijoDer.evaluarInterno(cantResultados);
					}
				}
				Funciones.evaluar(this);
				_limpiarResultados();
				break;
			default:
				
				if(this.hijoIzq	!= null)
				{
					IEvaluable subconjunto = this.hijoIzq._evaluarEspecial( (IEvaluable)this.valor );
					this.valor=subconjunto;
					//this.tipo es el mismo que el original
				}
				//else: lo deja igual y continúa
				break;
		}
	}

	/**
	 * Cuando un identificador tiene un iterador (i+2*3...), este método
	 *  saca el subconjunto de los valores que el iterador dicta.
	 * El 'this' de este método debe ser el EstructuraEvaluable que corresponde
	 *  al IEvaluable a iterar.
	 *@param valor corresponde al atributo del que se necesita sacar el subconjunto cuyos índices
	 *    están dictados por el iterador.
	 */
	protected IEvaluable _evaluarEspecial(IEvaluable valor)
	{	
		//subconjunto de valores que corresponde a la salida
		Valores valoresParciales=null;
		switch (valor.getTipo())
		{
			case BOOLEANO:
				valoresParciales = new Booleanos(valor.getNoCasos());
				tipo = DataObjectTipos.LISTA_BOOL;
				break;
			case ENTERO:
				valoresParciales = new Enteros(valor.getNoCasos());
				tipo = DataObjectTipos.LISTA_ENTERO;
				break;
			case FLOTANTE:
				valoresParciales = new Flotantes(valor.getNoCasos());
				tipo = DataObjectTipos.LISTA_FLOTANTE;
				break;
			case INTERVALO:
				valoresParciales = new Intervalos(valor.getNoCasos());
				tipo = DataObjectTipos.LISTA_RANGO;
				break;
			case CUALITATIVO:
				valoresParciales = new Cualitativos(valor.getNoCasos());
				tipo = DataObjectTipos.LISTA_CUALITATIVO;
				break;
		}
		
		//saca los índices que el iterador dicta
		for(int i =0; i<valor.getNoCasos(); i++)
		{
			EstructuraEvaluable clonPadre = this.clonar();
			EstructuraEvaluable iterador = clonPadre._buscarI();
			
			//   'i' = indice (que es entero)
			iterador.setValor(i);
			iterador.setTipo(DataObjectTipos.ENTERO);
			
			//    guarda el resultado
			clonPadre.evaluar(valor);
			Integer resultado = ((Number) clonPadre.valor).intValue();
			valoresParciales.setValor(i, valor.getValor(resultado));
		}
		
		return valoresParciales;
	}
	
	/**
	 * Busca en esta estructura evaluable el que tiene el identificador especial "i"
	 * @return el hijo con el identificador, {@code null} en caso contrario.
	 */
	private EstructuraEvaluable _buscarI()
	{
		String valor = this.valor.toString();
		if(valor.equals("i"))
			return this;
		if(this.hijoIzq != null)
		{
			EstructuraEvaluable delHijo = hijoIzq._buscarI();
			if(delHijo != null)
				return delHijo;
			if(hijoDer != null)
				return hijoDer._buscarI();
		}
		return null;
	}

	/**
	 * Dado que el campo "valor" de la EstructuraEvaluable corresponde a
	 * el resultado de su evaluación, debe limpiarse después de usado
	 * (cuando es nodo no raíz), para evitar desperdicios de memoria.  Por lo
	 * tanto, si un nodo no es raíz y tampoco es un nodo hoja, debe ser
	 * 'borrado' su valor de ejecución temporal.  Esta función se encarga de ello.
	 */
	protected void _limpiarResultados()
	{
		//cuando el hijo izquierdo no es hoja
		if(this.hijoIzq != null && this.hijoIzq.hijoIzq != null)
		{
			this.hijoIzq.valor = null;
			if(this.hijoDer != null && this.hijoDer.hijoIzq != null)
			{
				this.hijoDer.valor=null;
			}
		}
	}

	/**
	 * Devuelve el primer parámetro para la evaluación.
	 * @return EstructuraEvaluable objeto para sacar su valor para la evaluación.
	 */
	public EstructuraEvaluable getPrimerParametro()
	{
		return this.hijoIzq;
	}
	public void setPrimerParametro(EstructuraEvaluable ee)
	{
		this.hijoIzq = ee;
	}

	/**
	 * Devuelve el segundo parámetro para la evaluación.
	 * @return EstructuraEvaluable objeto para sacar su valor para la evaluación.
	 */
	public EstructuraEvaluable getSegundoParametro()
	{
		return this.hijoDer;
	}
	public void setSegundoParametro(EstructuraEvaluable ee)
	{
		this.hijoDer = ee;
	}

	/**
	 * Devuelve el id si el valor de esta estructura corresponde a una función
	 * @return EstructuraEvaluable objeto para sacar su valor para la evaluación.
	 */
	public int getIdFuncion()
	{
		return this.idFuncion;
	}
	public void setIdFuncion( int id)
	{
		this.idFuncion = id;
	}

	/**
	 * Este es el tipo actual de esta instacia.
	 * En el caso de usar primero evaluar(), da el tipo del resultado
	 * de la evaluación completa.
	 * @return DataObjectTipos el tipo actual de la instancia.
	 */
	public DataObjectTipos getTipo()
	{
		return this.tipo;
	}
	public void setTipo(DataObjectTipos tipo)
	{
		this.tipo = tipo;
	}

	/**
	 * Regresa el valor actual de esta instacia.
	 * En el caso de usar primero evaluar(), da el resultado
	 * de la evaluación completa.
	 * @return Object el valor actual de la instancia.
	 */
	public Object getValor()
	{
		return this.valor;
	}

	/**
	 * Cuando se le da un set al valor del nodo actual
	 * @param obj Nuevo valor para este nodo de la estructura
	 */
	public void setValor(Object obj)
	{
		this.valor = obj;
	}



	/**
	 * Auxliar de impresión recursiva.
	 * @param tabs representa el nivel del objeto en el árbol
	 */
	protected void _print(String tabs)
	{
		if(this.hijoIzq != null)
		{
			System.out.println(tabs+"id: "+this.idFuncion+ " valor actual: "+this.valor);
		}else
		{
			System.out.println(tabs+this.valor);
		}
		if(hijoIzq != null)
		{
			hijoIzq._print(tabs+"\t");

			if(hijoDer != null) hijoDer._print(tabs+"\t");
			else System.out.println("\t"+tabs+"---");
		}
	}

	public void imprimir()
	{
		this._print("");
	}

}
